package edu.pw.p2pclient.networking;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import edu.pw.p2pclient.data.Node;
import edu.pw.p2pclient.data.RemoteDeviceInfo;
import edu.pw.p2pclient.logic.AvailableManager;
import edu.pw.p2pclient.logic.DownloadManager;
import edu.pw.p2pclient.logic.SharedManager;
import edu.pw.p2pclient.networking.messages.HelloMessage;
import edu.pw.p2pclient.networking.messages.Message;
import edu.pw.p2pclient.networking.messages.Message.MsgType;
import edu.pw.p2pclient.networking.messages.ResourceListResponse;
import edu.pw.p2pclient.networking.messages.ResourcePartRequestMessage;
import edu.pw.p2pclient.networking.messages.ResourcePartResponse;

public class MessageParserThread extends Thread {

	public static final String TAG = "MessageParserThread";
	private MsgBuffer mBuffer = null;
	private boolean mRunning = false;
	private CommunicationService mCommService = null;

	private ResourceListResponse mResourceListResponse = null;

	public MessageParserThread(MsgBuffer buffer) {

		mBuffer = buffer;
		mCommService = CommunicationService.getInstance();
	}

	@Override
	public void run() {

		Thread.currentThread().setName(TAG);
		Log.i(TAG, "Starting MessageParserThread.");
		mBuffer.start();
		mRunning = true;
		Message messageToProcess;
		while (mRunning) {
			messageToProcess = mBuffer.take();
			if (!mRunning)
				return;
			if (messageToProcess == null)
				continue;
			switch (messageToProcess.getMsgType()) {
			case RESOURCE_LIST_REQUEST:
				Log.i(TAG, "New message RESOURCE_LIST_REQUEST");
				handleResourceListRequest(messageToProcess);
				break;
			case RESOURCE_LIST_RESPONSE:
				Log.i(TAG, "New message RESOURCE_LIST_RESPONSE");
				handleResourceListResponse((ResourceListResponse) messageToProcess);
				break;
			case RESOURCE_PART_REQUEST:
				Log.i(TAG, "New message RESOURCE_PART_REQUEST");
				handlePartRequest((ResourcePartRequestMessage) messageToProcess);
				break;
			case RESOURCE_PART_RESPONSE:
				// Should not come here
				Log.e(TAG,
						"New message RESOURCE_PART_RESPONSE that shouldn't come here.");
				break;
			case HELLO_MESSAGE:
				Log.i(TAG, "New message HELLO_MESSAGE");
				handleHelloMessage((HelloMessage) messageToProcess);
				break;
			case GOODBYE_MESSAGE:
				Log.i(TAG, "New message GOODBYE_MESSAGE");
				handleGoodbyeMessage(messageToProcess);
				break;
			case DENIAL:
				Log.i(TAG, "New message DENIAL");
				break;
			default:
				Log.e(TAG, "New message UNKNOWN");
				break;
			}
			messageToProcess = null;
		}
		mBuffer.endWork();
		mCommService.getHandler().sendMessage(
				android.os.Message.obtain(mCommService.getHandler(),
						CommunicationService.MESSAGE_PARSER_ENDED));
	}

	@Override
	public void interrupt() {

		Log.i(TAG, "Thread interrupted.");
		mRunning = false;
		mBuffer.endWork();
		super.interrupt();
	}

	private void handleResourceListRequest(Message message) {

		handleResourceListRequest(message,
				mCommService.getKnownDevice(message.getSenderAddress()), 0);
	}

	private void handleResourceListRequest(Message message,
			RemoteDeviceInfo clientDevice, int tries) {

		if (clientDevice != null) {
			Socket sock = clientDevice.getSocket();
			if (sock != null && !sock.isClosed()) {
				if (mResourceListResponse == null
						|| mResourceListResponse.getResourcesVersion() != SharedManager
								.getInstance().getListVersion())
					mResourceListResponse = new ResourceListResponse();
				try {
					Log.i(TAG, "Sending RESOURCE_LIST_RESPONSE to "
							+ clientDevice.getSocket().toString());
					clientDevice.writeObject(mResourceListResponse);
				} catch (IOException e) {
					// error creating outputStream
					e.printStackTrace();
					try {
						clientDevice.setSocket(null);
						sock.close();
					} catch (IOException ex) {
						// it can fail, just like a second ago - do not care
					}
					if (tries < 5)
						handleResourceListRequest(message, clientDevice,
								++tries);
				}
			} else {
				Log.i(TAG, "Socket is null. We will reconnect!");
				try {
					sock = new Socket(message.getSenderAddress(),
							CommunicationService.PORT);
					sock.setKeepAlive(true);
				} catch (IOException e) {
					e.printStackTrace();
					sock = null;
				} finally {
					try {
						clientDevice.setSocket(sock);
					} catch (IOException ex) {
						ex.printStackTrace();
					}
				}
				if (tries < 5)
					handleResourceListRequest(message, clientDevice, ++tries);
			}
		} else {
			// We do not know a client who writes to us. Almost impossible,
			// but...
			Log.e(TAG, "We do not know a client...");
			RemoteDeviceInfo newDevice = new RemoteDeviceInfo(
					message.getSenderAddress());
			mCommService.addKnownDevice(newDevice);
			if (tries < 5)
				handleResourceListRequest(message, newDevice, ++tries);
		}
	}

	private void handleResourceListResponse(ResourceListResponse message) {

		RemoteDeviceInfo device = mCommService.getKnownDevice(message
				.getSenderAddress());
		if (device == null) {
			Log.i(TAG, "We do not know a client...");
			device = new RemoteDeviceInfo(message.getSenderAddress());
			mCommService.addKnownDevice(device);
		}
		long oldVersion = device.getResourcesVersion();
		if (message.getResourcesVersion() > oldVersion)
			device.setResourcesVersion(message.getResourcesVersion());
		device.setResources(message.getResources());
		AvailableManager.getInstance().updateResources(device);
		DownloadManager.getInstance().onNewResourceListVersionObtained(device);
	}

	private void handleHelloMessage(HelloMessage message) {

		RemoteDeviceInfo info = mCommService.getKnownDevice(message
				.getSenderAddress());

		long oldVersion = (info != null ? info.getResourcesVersion()
				: SharedManager.UNKNOWN_RESOURCE_LIST_VERSION);
		Log.i(TAG, "message: " + String.valueOf(message.getListVersion())
				+ ", known: " + String.valueOf(oldVersion));
		if (info == null || message.getListVersion() > oldVersion) {
			Socket sock = null;
			boolean sent = false;
			int tries = 0;
			while (!sent && tries < 5) {
				try {
					if (info != null)
						sock = info.getSocket();
					if (sock == null || sock.isClosed()) {
						sock = new Socket(message.getSenderAddress(),
								CommunicationService.PORT);
						sock.setKeepAlive(true);
						if (info != null)
							info.setSocket(sock);
					}
					if (info == null) {
						info = new RemoteDeviceInfo(message.getSenderAddress(),
								sock);
						mCommService.addKnownDevice(info);
						mCommService.startNewHandler(info);
						info.writeObject(new ResourceListResponse());
					}
					Log.i(TAG,
							"Sending RESOURCE_LIST_REQUEST to "
									+ info.getAddress());
					info.writeObject(new Message(MsgType.RESOURCE_LIST_REQUEST));
					sent = true;
				} catch (IOException e) {
					e.printStackTrace();
					++tries;
					if (sock != null)
						try {
							Log.i(TAG, "Closing Socket");
							sock.close();
						} catch (IOException e1) {
							e1.printStackTrace();
						}
				}
			}
		}
	}

	private void handleGoodbyeMessage(Message message) {

		RemoteDeviceInfo device = mCommService.getKnownDevice(message
				.getSenderAddress());
		mCommService.removeKnownDevice(device);
		AvailableManager.getInstance().deviceRemoved(device);
	}

	@SuppressLint("NewApi")
	private void handlePartRequest(final ResourcePartRequestMessage message) {

		AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {

			@Override
			protected Void doInBackground(Void... params) {
				handlePartRequest(message,
						mCommService.getKnownDevice(message.getSenderAddress()));
				return null;
			}

		};
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
			task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
		} else {
			task.execute();
		}
	}

	private void handlePartRequest(ResourcePartRequestMessage message,
			RemoteDeviceInfo clientDevice) {

		if (clientDevice != null) {
			Socket sock = null;
			ObjectOutputStream ostr = null;
			ObjectInputStream instr = null;
			Message toBeSent = null;
			Node res = SharedManager.getInstance().getShared(
					message.getResource());
			if (res != null
					&& (!res.getSharingInfo().isPasswordProtected() || res
							.getSharingInfo()
							.getPassword()
							.equals(message.getResource().getSharingInfo()
									.getPassword()))) {
				Log.i(TAG, "Will send part " + message.getPart()
						+ " of resource " + res.getFullPath());
				toBeSent = new ResourcePartResponse(res, message.getPart());
			} else {
				Log.i(TAG, "Will send DENIAL as result isn't shared anymore.");
				toBeSent = new Message(MsgType.DENIAL);
			}
			try {
				Log.i(TAG,
						"Sending part response to "
								+ message.getSenderAddress() + ":"
								+ message.getPort());
				sock = new Socket(message.getSenderAddress(), message.getPort());
				ostr = new ObjectOutputStream(sock.getOutputStream());
				ostr.writeObject(toBeSent);
				instr = new ObjectInputStream(sock.getInputStream());
				instr.readObject();
			} catch (IOException e) {
				e.printStackTrace();
				if (ostr != null) {
					try {
						ostr.close();
					} catch (IOException ex) {
					}
				}
				if (instr != null) {
					try {
						instr.close();
					} catch (IOException ex) {
					}
				}
				if (sock != null) {
					try {
						sock.close();
					} catch (IOException ex) {
					}
				}
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				if (ostr != null) {
					try {
						ostr.close();
					} catch (IOException ex) {
					}
				}
				if (instr != null) {
					try {
						instr.close();
					} catch (IOException ex) {
					}
				}
				if (sock != null) {
					try {
						sock.close();
					} catch (IOException ex) {
					}
				}
			}
		} else {
			// We do not know a client who writes to us. Almost impossible,
			// but...
			Log.e(TAG, "We do not know a client...");
			RemoteDeviceInfo newDevice = new RemoteDeviceInfo(
					message.getSenderAddress());
			mCommService.addKnownDevice(newDevice);
			handlePartRequest(message, newDevice);
		}
	}
}
